﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Velocity4Net.Runtime.Directives
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */









    /**
     * This handles context scoping and metadata for directives.
     *
     * @author Nathan Bubna
     * @version $Id$
     */
    public class Scope : Hashtable
    {
        private const String setReturnValue = "";
        private IDictionary storage;
        private Object replaced;
        private Scope parent;
        private Info info;
        protected readonly Object owner;

        /**
         * @param owner
         * @param previous
         */
        public Scope(Object owner, Object previous)
        {
            this.owner = owner;
            if (previous != null)
            {
                try
                {
                    this.parent = (Scope)previous;
                }
                catch (InvalidCastException cce)
                {
                    this.replaced = previous;
                }
            }
        }

        private IDictionary getStorage()
        {
            if (storage == null)
            {
                storage = new Hashtable();
            }
            return storage;
        }

        /**
         * @return entry set
         */
        //public ISet entrySet()
        //{
        //    return getStorage().entrySet();
        //}

        /**
         * getter
         * @param key
         * @return found value
         */

        public Object get(Object key)
        {
            Object o = base[key];
            if (o == null && parent != null && !ContainsKey(key))
            {
                return parent.get(key);
            }
            return o;
        }

        /**
         * setter
         * @param key
         * @param value
         * @return previous value
         */

        public Object put(Object key, Object value)
        {
            getStorage().Add(key, value);
            return value;
        }

        /**
         * Convenience method to call put(key,val) in a template
         * without worrying about what is returned/rendered by the call.
         * This should ALWAYS return an empty string.
         * @param key
         * @param value
         * @return empty string
         */
        public String set(Object key, Object value)
        {
            put(key, value);
            return setReturnValue;
        }

        /**
         * Allows #stop to easily trigger the proper StopCommand for this scope.
         */
        protected void stop()
        {
            throw new StopCommand(owner);
        }

        /**
         * Returns the number of control arguments of this type
         * that are stacked up.  This is the distance between this
         * instance and the topmost instance, plus one. This value
         * will never be negative or zero.
         * @return depth
         */
        protected int getDepth()
        {
            if (parent == null)
            {
                return 1;
            }
            return parent.getDepth() + 1;
        }

        /**
         * Returns the topmost parent control reference, retrieved
         * by simple recursion on {@link #getParent}.
         * @return top-most scope
         */
        public Scope getTopmost()
        {
            if (parent == null)
            {
                return this;
            }
            return parent.getTopmost();
        }

        /**
         * Returns the parent control reference overridden by the placement
         * of this instance in the context.
         * @return parent scope
         */
        public Scope getParent()
        {
            return parent;
        }

        /**
         * Returns the user's context reference overridden by the placement
         * of this instance in the context.  If there was none (as is hoped),
         * then this will return null.  This never returns parent controls;
         * those are returned by {@link #getParent}.
         * @return replaced reference value, or null
         */
        public Object getReplaced()
        {
            if (replaced == null && parent != null)
            {
                return parent.getReplaced();
            }
            return replaced;
        }

        /**
         * Returns info about the current scope for debugging purposes.
         * @return template debugging infos
         */
        public Info getInfo()
        {
            if (info == null)
            {
                info = new Info(this, owner);
            }
            return info;
        }

        /**
         * Class to encapsulate and provide access to info about
         * the current scope for debugging.
         */
        public class Info
        {
            private Scope scope;
            private Directive directive;
            private Template template;

            /**
             * c'tor
             * @param scope
             * @param owner
             */
            public Info(Scope scope, Object owner)
            {
                if (owner is Directive)
                {
                    directive = (Directive)owner;
                }
                if (owner is Template)
                {
                    template = (Template)owner;
                }
                this.scope = scope;
            }

            /**
             * name getter
             * @return name
             */
            public String getName()
            {
                if (directive != null)
                {
                    return directive.getName();
                }
                if (template != null)
                {
                    return template.getName();
                }
                return null;
            }

            /**
             * type getter
             * @return scope type
             */
            public String getType()
            {
                if (directive != null)
                {
                    switch (directive.getType())
                    {
                        case Directive.BLOCK:
                            return "block";
                        case Directive.LINE:
                            return "line";
                    }
                }
                if (template != null)
                {
                    return template.getEncoding();
                }
                return null;
            }

            /**
             * current depth
             * @return depth
             */
            public int getDepth()
            {
                return scope.getDepth();
            }

            /**
             * template name getter
             * @return template name
             */
            public String getTemplate()
            {
                if (directive != null)
                {
                    return directive.getTemplateName();
                }
                if (template != null)
                {
                    return template.getName();
                }
                return null;
            }

            /**
             * line getter
             * @return line number
             */
            public int getLine()
            {
                if (directive != null)
                {
                    return directive.getLine();
                }
                return 0;
            }

            /**
             * column getter
             * @return column number
             */
            public int getColumn()
            {
                if (directive != null)
                {
                    return directive.getColumn();
                }
                return 0;
            }

            /**
             * string representation getter
             * @return string representation
             */
            public String toString()
            {
                StringBuilder sb = new StringBuilder();
                if (directive != null)
                {
                    sb.Append('#'); // parser characters substitution is not handled here
                }
                sb.Append(getName());
                sb.Append("[type:").Append(getType());
                int depth = getDepth();
                if (depth > 1)
                {
                    sb.Append(" depth:").Append(depth);
                }
                if (template == null)
                {
                    String vtl = getTemplate();
                    sb.Append(" template:");
                    if (!vtl.Contains(" "))
                    {
                        sb.Append(vtl);
                    }
                    else
                    {
                        sb.Append('"').Append(vtl).Append('"');
                    }
                    sb.Append(" line:").Append(getLine());
                    sb.Append(" column:").Append(getColumn());
                }
                sb.Append(']');
                return sb.ToString();
            }
        }

    }
}